
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Forms;
using ARDominos;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using GoblinXNA;
using GoblinXNA.Graphics;
using GoblinXNA.SceneGraph;
using Model = GoblinXNA.Graphics.Model;
using GoblinXNA.Graphics.Geometry;
using GoblinXNA.Device.Generic;
using GoblinXNA.Physics;
using GoblinXNA.Physics.Newton1;
using GoblinXNA.Sounds;
using GoblinXNA.Helpers;
using GoblinXNA.Shaders;

using GoblinXNA.UI.UI2D;

using GoblinXNA.Device.Capture;
using GoblinXNA.Device.Vision;
using GoblinXNA.Device.Vision.Marker;
using GoblinXNA.Device.Util;
using Keys = Microsoft.Xna.Framework.Input.Keys;

namespace _3DAR
{
    public class AREditor : Microsoft.Xna.Framework.Game
    {
        #region Member Fields
        GraphicsDeviceManager graphics;

        // A GoblinXNA scene graph
        static Scene scene;

        // A marker node for tracking the ground plane (game board)
        MarkerNode _groundMarkerNode;


        // Current game state including the modes, play time, etc
        public GameState GameState;

        // Whether to display the GUI for mode selection
        bool showGUI = false;

        // The UI manager which draws the 2D GUIs
        static UIManager uiManager;

        // A basic effect used to draw the line
        // (only used in AdditionMode.LineDrawing mode)
        BasicEffect basicEffect;

        // Whether to always shoot from the center of the screen
        bool shootCenterMode = false;

        #endregion

        private PrimitiveGeometryCreator _primitiveGeometryCreator;
        private Cursor _cursor;
        private Transformator _transformator;
        private Model animatedModel;

        #region Constructor
        public AREditor()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            graphics.PreferredBackBufferWidth = 800;
            graphics.PreferredBackBufferHeight = 600;
        }
        #endregion

        internal ARDominos.Transformator Transformator
        {
            get
            {
                throw new System.NotImplementedException();
            }
            set
            {
            }
        }

        internal Cursor Cursor
        {
            get
            {
                throw new System.NotImplementedException();
            }
            set
            {
            }
        }

        internal PrimitiveGeometryCreator PrimitiveGeometryCreator
        {
            get
            {
                throw new System.NotImplementedException();
            }
            set
            {
            }
        }

        public UIManager UIManager
        {
            get
            {
                throw new System.NotImplementedException();
            }
            set
            {
            }
        }

        #region Override Methods
        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();

            // Initialize the GoblinXNA framework
            State.InitGoblin(graphics, Content, "");

            //State.ThreadOption = (ushort)ThreadOptions.MarkerTracking;

            // Initialize the scene graph
            scene = new Scene();

            GameState = new GameState();
            uiManager = new UIManager(GameState);

            // Setup for physics simulation
            //SetupPhysics();

            // Setup marker tracking using ALVAR tracking library
            SetupMarkerTracking();

            // Enable the shadow mapping
            scene.ShadowMap = new MultiLightShadowMap();

            this._primitiveGeometryCreator = new PrimitiveGeometryCreator(scene.ShadowMap, _groundMarkerNode);
            
            CreateCursor();
            this._transformator = new Transformator(this._groundMarkerNode, this._cursor.CursorMarkerNode);

            // Set up the lights used in the scene
            CreateLights();

            // Create 3D objects
            CreateObject();

            // Initialize the UI manager
            uiManager.Initialize(scene, GameModeSwitched, ExtraModeSwitched);
            uiManager.LoadContent();

            // Show Frames-Per-Second on the screen for debugging
            State.ShowFPS = true;

            MouseInput.Instance.MousePressEvent += new HandleMousePress(MousePressHandler);
            MouseInput.Instance.MouseDragEvent += new HandleMouseDrag(MouseDragHandler);
            MouseInput.Instance.MouseReleaseEvent += new HandleMouseRelease(MouseReleaseHandler);
            MouseInput.Instance.MouseMoveEvent += new HandleMouseMove(MouseMoveHandler);

            KeyboardInput.Instance.KeyPressEvent += new HandleKeyPress(KeyPressHandler);
            KeyboardInput.Instance.KeyReleaseEvent += new HandleKeyRelease(KeyReleaseHandler);

            // Create a basic effect to draw the line for AdditionMode.LineDrawing
            basicEffect = new BasicEffect(graphics.GraphicsDevice);
            basicEffect.DiffuseColor = new Vector3(1.0f, 0.0f, 0.0f);

        }


        protected override void Dispose(bool disposing)
        {
            scene.Dispose();
        }

        protected override void Update(GameTime gameTime)
        {
            if (GameState.CurrentGameMode == GameState.GameMode.Add)
            {
                if (this._primitiveGeometryCreator.ToUpdate)
                    this._primitiveGeometryCreator.UpdateCreating(this._cursor.PositionRelativeScene);
            }
            scene.Update(gameTime.ElapsedGameTime, gameTime.IsRunningSlowly, this.IsActive);
        }

        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            // Renders GUI
            uiManager.Draw(gameTime);

            // Processes and renders the scene graph
            scene.Draw(gameTime.ElapsedGameTime, gameTime.IsRunningSlowly);
        }
        #endregion

        #region Input Handlers
        private void MouseMoveHandler(Point mouseLocation)
        {
                // If shootCenterMode is not true, then make the cross hair follow the mouse
                // Otherwise, the cross hair cursor always stays at the center of the screen
                if(!shootCenterMode)
                    uiManager.CrossHairPoint = mouseLocation;
        }

        private void MouseReleaseHandler(int button, Point mouseLocation)
        {
            // If the GUI is shown, and the mouse is within the bound of the GUI, then
            // don't perform the mouse press event for the scene
            if (uiManager.Frame.Visible && UI2DHelper.IsWithin(mouseLocation, uiManager.Frame.Bounds))
                return;

            if (GameState.CurrentGameMode == GameState.GameMode.Add)
            {
                this._primitiveGeometryCreator.StopCreating(this._cursor.PositionRelativeScene);
            }
            else if (GameState.CurrentGameMode == GameState.GameMode.Transform)
            {
                this._transformator.Stop(this._cursor.CursorMarkerNode.WorldTransformation *
                         (Matrix.Invert(this._groundMarkerNode.WorldTransformation)));
            }
        }

        private void MouseDragHandler(int button, Point startLocation, Point currentLocation)
        {
            // If the GUI is shown, and the mouse is within the bound of the GUI, then
            // don't perform the mouse press event for the scene
            if (uiManager.Frame.Visible && UI2DHelper.IsWithin(currentLocation, uiManager.Frame.Bounds))
                return;
        }

        private void MousePressHandler(int button, Point mouseLocation)
        {
            // If the GUI is shown, and the mouse is within the bound of the GUI, then
            // don't perform the mouse press event for the scene
            if(uiManager.Frame.Visible && UI2DHelper.IsWithin(mouseLocation, uiManager.Frame.Bounds))
                return;

            if (GameState.CurrentGameMode == GameState.GameMode.Add)
            {
                // Add a domino on the ground
                if (button == MouseInput.LeftButton)
                {
                    this._primitiveGeometryCreator.StartCreating(this._cursor.PositionRelativeScene, GameState.CurrentAddMode);
                }
            }
            else if (GameState.CurrentGameMode == GameState.GameMode.Transform)
            {
                if (button == MouseInput.LeftButton)
                {
                    this._transformator.Start(this._cursor.CursorMarkerNode.WorldTransformation *
                     (Matrix.Invert(this._groundMarkerNode.WorldTransformation)));
                }
            }
        }

        private void LoadModel()
        {
            ModelLoader loader = new ModelLoader();
            animatedModel = (Model)loader.Load("","external_model");
            animatedModel.UseInternalMaterials = true;

            GeometryNode modelNode = new GeometryNode("Dude");
            modelNode.Model = animatedModel;
            this._groundMarkerNode.AddChild(modelNode);
        }

       

        private void KeyReleaseHandler(Keys key, KeyModifier modifier)
        {
        }

        private void KeyPressHandler(Keys key, KeyModifier modifier)
        {
            if (key == Keys.Escape)
                this.Exit();

            if (key == Keys.S)
            {
                scene.EnableShadowMapping = !scene.EnableShadowMapping;
            }

            if (key == Keys.G)
            {
                showGUI = !showGUI;
                if (showGUI)
                {
                    uiManager.Frame.Visible = true;
                    uiManager.Frame.Enabled = true;
                    uiManager.ModeChoiceEnabled = false;
                }
                else
                {
                    uiManager.Frame.Visible = false;
                    uiManager.Frame.Enabled = false;
                }
            }
            else if (key == Keys.A)
                uiManager.GameAdd.DoClick();
            else if (key == Keys.R)
                ResetGame();
            else if (key == Keys.H)
                uiManager.EnableHelpMenu = !uiManager.EnableHelpMenu;
            else if (key == Keys.T)
            {
                GameState.CurrentGameMode = GameState.GameMode.Transform;
                uiManager.SwitchMode();
            }
            else if (key == Keys.L)
                LoadModel();
            if (GameState.CurrentGameMode == GameState.GameMode.Add)
            {
                if (key == Keys.D1)
                    GameState.CurrentAddMode = GameState.AddMode.Box;
                else if (key == Keys.D2)
                    GameState.CurrentAddMode = GameState.AddMode.Sphere;
            }
        }
        #endregion
 
        #region Scene Creation
        private void CreateLights()
        {
            // Create a directional light source
            LightSource lightSource = new LightSource();
            lightSource.Direction = new Vector3(-1.0f, -1.0f, -1.0f);
            lightSource.Diffuse =   Color.White.ToVector4();
            lightSource.Specular = new Vector4(.60f, 0.60f, 0.60f, 1);
            lightSource.Position = new Vector3(0, 1000, 0);

            // Create a light node to hold the light source
            LightNode lightNode = new LightNode();
            lightNode.CastShadows = true;
            lightNode.LightProjection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, 1, 1, 500);
            lightNode.AmbientLightColor = new Vector4(0.6f, .6f, 0.6f, 1);
            lightNode.LightSource = lightSource;

            scene.RootNode.AddChild(lightNode);

        }

        private void SetupMarkerTracking()
        {
            DirectShowCapture captureDevice = new DirectShowCapture();
            try
            {
                captureDevice.InitVideoCapture(0, FrameRate._30Hz, Resolution._640x480, ImageFormat.R8G8B8_24, false);
            }
            catch (GoblinException ex)
            {
                MessageBox.Show("Nie wykryto kamery.");
            }
            scene.AddVideoCaptureDevice(captureDevice);

            ALVARMarkerTracker tracker = new ALVARMarkerTracker();
            tracker.MaxMarkerError = 0.02f;
            tracker.InitTracker(captureDevice.Width, captureDevice.Height, "calib.xml", 9.0);

            scene.MarkerTracker = tracker;

            scene.ShowCameraImage = true;

            //scene.PhysicsEngine.GravityDirection = -Vector3.UnitZ;

            // Create a marker node to track the ground plane
            _groundMarkerNode = new MarkerNode(scene.MarkerTracker, "ground markers.txt");

            //building tree
            scene.RootNode.AddChild(_groundMarkerNode);


        }

        private void CreateObject()
        {
            CreateGround();
            
        }

        

        private void CreateGround()
        {
            GeometryNode groundNode = new GeometryNode("Ground");
            groundNode.Model = new TexturedBox(129.5f, 99, 0.2f);

            groundNode.Physics.Collidable = true;
            groundNode.Physics.Shape = ShapeType.Box;
            groundNode.Physics.MaterialName = "Ground";
            groundNode.Physics.Pickable = true;
            groundNode.AddToPhysicsEngine = true;
            groundNode.IsOccluder = true;

            groundNode.Model.ShadowAttribute = ShadowAttribute.ReceiveOnly;
            groundNode.Model.Shader = new SimpleShadowShader(scene.ShadowMap);

            Material groundMaterial = new Material();
            groundMaterial.Diffuse = new Vector4(0.5f, 0.5f, 0.5f, 0.5f);
            groundMaterial.Specular = Color.White.ToVector4();
            groundMaterial.SpecularPower = 20;

            groundNode.Material = groundMaterial;

            _groundMarkerNode.AddChild(groundNode);
        }

        private void CreateCursor()
        {
            _cursor = new Cursor(scene, this._primitiveGeometryCreator, this._groundMarkerNode);
        }

        

        /// <summary>
        /// Resets the game to the initial state with unmodified game board (all of the added, removed
        /// or editted dominos are reset to the initial domino layout)
        /// </summary>
        private void ResetGame()
        {
            uiManager.GameAdd.DoClick();
        }

        #endregion

        #region Action Listeners
        /// <summary>
        /// An action event handler for extra mode switch
        /// </summary>
        /// <param name="source"></param>
        private void ExtraModeSwitched(object source)
        {

        }

        /// <summary>
        /// An action event handler for game mode switch
        /// </summary>
        private void GameModeSwitched(object source)
        {
            if (((G2DRadioButton)source).Text.Equals("Add"))
            {

                GameState.CurrentGameMode = GameState.GameMode.Add;

                uiManager.SwitchMode();
            }
        }
        #endregion

    }
}
